﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RenderEngineInterfaces;
using System.ComponentModel.Composition;
using DeepZoomPivotConstructor.VisualLib;

namespace RenderEngine
{
    /// <summary>
    /// Helper class to do various layout helper things.
    /// </summary>
    [Export(typeof(ILayoutHelper))]
    public class LayoutHelper : ILayoutHelper
    {
        public LayoutHelper()
        {
            ///
            /// Default settings
            /// 

            DefaultSlidesPerRow = 10;
            XBoarderSize = 0.2;
            YBoarderSize = 0.2;
        }

        /// <summary>
        /// Goal for # of slides per row.
        /// </summary>
        public int DefaultSlidesPerRow { get; set; }

        /// <summary>
        /// The X boarder size in units
        /// </summary>
        public double XBoarderSize { get; set; }

        /// <summary>
        /// The Y boarder size in units
        /// </summary>
        public double YBoarderSize { get; set; }

        /// <summary>
        /// Take a list of visuals, lay them out in a row
        /// </summary>
        /// <param name="visuals"></param>
        /// <returns></returns>
        public Visual LayoutIn2By3Rows(IEnumerable<Visual> visuals)
        {
            ///
            /// Attempt to layout things in a 2:3 style layout (assuming everything is about the same
            /// size).
            /// 

            var allVisuals = visuals.ToArray();

            int nVisuals = allVisuals.Count();

            int targetCols = 3;
            if (nVisuals > targetCols)
            {
                double twoby3factor = Math.Sqrt(3.0 / 2.0 * nVisuals);
                targetCols = (int) (twoby3factor + 0.5);
            }

            int ncols = CalcColumnsCloseToTarget(allVisuals.Count(), targetCols);
            return LayoutByColumns(ncols, visuals.ToArray());
#if false
            ///
            /// Lets hope chunking isn't required for this latest version of the rendering!
            ///
            var slideChunks = from vgroup in visuals.ToList().GetListAsChunks(ncols * MaxRowsPerTalkChunk)
                              select vgroup;

            var visualChunks = from s in slideChunks
                               select LayoutByColumns(ncols, s.ToArray());

            ///
            /// What we do next depends on how many of these chunks we have run into. If just one
            /// then things are pretty easy.
            /// 

            var visualChunksAsArray = visualChunks.ToArray();
            if (visualChunksAsArray.Length == 1)
            {
                return visualChunksAsArray[0];
            }

            ///
            /// Mark them all as a sub-composition, and lay them out in a holder, and
            /// return that!
            /// 

            VisualHolder allChunks = new VisualHolder();
            double offset = 0.0;
            foreach (var v in visualChunksAsArray)
            {
                VisualBitmapImage bm = new VisualBitmapImage(v.Width, v.Height,
                    10, TalkBackgroundColor);

                VisualHolder subImages = new VisualHolder();
                v.IncZIndex();
                subImages.AddImage(v);
                subImages.AddImage(bm);

                subImages.IsCompositionUnit = true;
                subImages.YPosition = offset;
                allChunks.AddImage(subImages);
                offset = allChunks.Height + YBoarderSize;
            }

            return allChunks;
#endif
        }

        /// <summary>
        /// Do the actual layout work when we are doing things by columns, cross first, then down.
        /// </summary>
        /// <param name="visualImages">A list of the images to layout</param>
        /// <param name="ncols">Number of columns to use in the layout</param>
        /// <returns></returns>
        private Visual LayoutByColumns(int ncols, Visual[] visualImages)
        {
            double current_x_pos = 0.0F;
            double current_y_pos = 0.0F;
            double next_y_pos = 0.0F;
            int current_column = 0;
            int picture_index = 0;
            Func<Visual, Visual> placeit = s =>
            {
                s.XPosition = current_x_pos;
                s.YPosition = current_y_pos;
                s.IncZIndex(picture_index);

                picture_index += 1;

                var bb = s.VisualBoundingBox;

                double ynext = current_y_pos + bb.Height;
                if (ynext > next_y_pos)
                {
                    next_y_pos = ynext;
                }
                current_x_pos += bb.Width + XBoarderSize;
                current_column += 1;
                if (current_column >= ncols)
                {
                    current_column = 0;
                    current_x_pos = 0.0F;
                    current_y_pos = next_y_pos + YBoarderSize;
                }
                return s;
            };

            var placedSlides = from s in visualImages
                               select placeit(s);

            VisualHolder result = new VisualHolder();
            foreach (var v in placedSlides)
            {
                result.AddImage(v);
            }
            return result;
        }

        /// <summary>
        /// Calculate how many columns we should render this talk in given the number of slides, along with
        /// a target number we'd like to aim for.
        /// </summary>
        /// <param name="nslides"></param>
        /// <param name="targetColumns"></param>
        /// <returns></returns>
        private int CalcColumnsCloseToTarget(int nslides, int targetColumns)
        {
            /// Should it all be on one line? Yes, if it is smaller than the target!

            if (nslides <= targetColumns)
            {
                return nslides;
            }

            /// Now, we move +- 20% of the given target and see what fits the
            /// best with what we are given.

            int delta = (int)((float)targetColumns * 0.2F);

            var allofthem = from ncol in CalcDeltaRange(targetColumns, delta)
                            orderby ncol - SlidesInLastRow(nslides, ncol)
                            select ncol;

            return allofthem.First();
        }
        /// <summary>
        /// Calc the number of slides in the last row given # of slides and # of columns. Helps
        /// calc the optimal layout.
        /// </summary>
        /// <param name="nslides"></param>
        /// <param name="ncol"></param>
        /// <returns></returns>
        private int SlidesInLastRow(int nslides, int ncol)
        {
            int remainder = nslides % ncol;
            if (remainder == 0)
            {
                return ncol;
            }
            return remainder;
        }

        /// <summary>
        /// Calc a delta array we can iterate over.
        /// </summary>
        /// <param name="targetColumns"></param>
        /// <param name="delta"></param>
        /// <returns></returns>
        private int[] CalcDeltaRange(int targetColumns, int delta)
        {
            int[] result = new int[delta * 2 + 1];

            int index = 0;
            for (int i = -delta; i <= delta; i++)
            {
                result[index] = targetColumns + i;
                index = index + 1;
            }

            return result;
        }
    }
}
